﻿
namespace Document.Library
{
    using System.IO;
    using System;
    using ICSharpCode.SharpZipLib.Zip;

    /// <summary>
    /// 使用SharpZipLib实现的压缩服务
    /// </summary>
    public class ZipServiceBySharpZipLib : IZipFileService
    {
        #region 属性和构造
        /// <summary>
        /// 压缩配置
        /// </summary>
        private ZipConfig _config;

        /// <summary>
        /// 获取压缩配置
        /// </summary>
        public ZipConfig Config => _config;

        /// <summary>
        /// 不公开默认构造函数，请通过InitZipService来创建实例
        /// </summary>
        private ZipServiceBySharpZipLib() { }

        /// <summary>
        /// 初始化服务
        /// </summary>
        /// <param name="config">配置对象</param>
        /// <returns>当前压缩服务</returns>
        public static ZipServiceBySharpZipLib InitZipService(ZipConfig config)
        {
            ZipServiceBySharpZipLib ser = new ZipServiceBySharpZipLib();
            ser._config = config;
            return ser;
        }
        #endregion

        #region IZipFileService的实现

        /// <summary>
        /// 压缩目录
        /// </summary>        
        public void ZipDirectory()
        {
            try
            {
                /**
                  * 校验要压缩的目录是否存在
                  */
                if (!Directory.Exists(Config.DirectoryPath))
                    throw new DirectoryNotFoundException($"要压缩的目录{Config.DirectoryPath}未找到。");

                var outputDirectoryPath = Path.GetDirectoryName(Config.OutputFilePath);

                /**
                 * 校验输出的文件目录是否存在
                 */
                if (!Directory.Exists(outputDirectoryPath))
                    throw new DirectoryNotFoundException($"输出路径的文件目录{outputDirectoryPath}未找到。");

                using (ZipOutputStream zipedOutputStream = new ZipOutputStream(File.Create(Config.OutputFilePath)))
                {
                    // 判断是否需要对文件加密
                    if (!string.IsNullOrEmpty(Config.Password))
                        zipedOutputStream.Password = Config.Password;

                    // 压缩该目录文件
                    ZipDirectoryFiles(Config.DirectoryPath, zipedOutputStream);

                    if (Config.IsIncludeSubDir)
                    {
                        var subDirectories = Directory.GetDirectories(Config.DirectoryPath);

                        if (subDirectories.Length > 0)
                        {
                            foreach (var subDirectoryPath in subDirectories)
                            {
                                // 压缩子目录
                                ZipSubDirectories(subDirectoryPath, zipedOutputStream);
                            }
                        }
                    }
                    zipedOutputStream.Finish();
                }
            }
            catch //(Exception ex)
            {
                throw new Exception("压缩目录出错");
            }
        }

        /// <summary>
        /// 压缩单个文件
        /// </summary>        
        public void ZipSingleFile()
        {
            try
            {
                /**
                                * 校验要压缩的目录是否存在
                                */
                if (!File.Exists(Config.ToZipedSingleFilePath))
                    throw new DirectoryNotFoundException($"要压缩的文件{Config.DirectoryPath}未找到。");

                var outputDirectoryPath = Path.GetDirectoryName(Config.OutputFilePath);

                /**
                 * 校验输出的文件目录是否存在
                 */
                if (!Directory.Exists(outputDirectoryPath))
                    throw new DirectoryNotFoundException($"输出路径的文件目录{outputDirectoryPath}未找到。");

                /**
                 * 创建压缩输出流
                 */
                using (ZipOutputStream zipedOutputStream = new ZipOutputStream(File.Create(Config.OutputFilePath)))
                {
                    // 判断是否需要对文件加密
                    if (!string.IsNullOrEmpty(Config.Password))
                        zipedOutputStream.Password = Config.Password;

                    using (FileStream fs = File.OpenRead(Config.ToZipedSingleFilePath))
                    {
                        // 创建压缩条目
                        ZipEntry entry = new ZipEntry(Path.GetFileName(Config.ToZipedSingleFilePath));
                        entry.DateTime = DateTime.Now;
                        entry.Size = fs.Length;
                        zipedOutputStream.PutNextEntry(entry);

                        // 设置默认缓冲大小和记录读取长度的变量
                        var blockSize = 2048;
                        var rededBytesCount = 0;
                        var totalRededBytesCount = 0;

                        // 读取的文件字节数组
                        var fileBytes = new byte[fs.Length];

                        while (totalRededBytesCount < fs.Length)
                        {
                            // 如果剩余字节流长度不够了就设置为剩余的长度，否则就是缓冲的块大小
                            blockSize = fs.Length - totalRededBytesCount > blockSize ? blockSize : (int)(fs.Length - totalRededBytesCount);
                            rededBytesCount = fs.Read(fileBytes, totalRededBytesCount, blockSize);
                            totalRededBytesCount += rededBytesCount;
                        }

                        // 写入压缩文件                    
                        zipedOutputStream.Write(fileBytes, 0, fileBytes.Length);
                        zipedOutputStream.Flush();
                    }
                    zipedOutputStream.Finish();
                }
            }
            catch //(Exception)
            {
                throw new Exception("压缩单个文件出错");
            }
        }

        #endregion

        #region 实现的压缩方法

        /// <summary>
        /// 压缩目录下的文件
        /// </summary>
        /// <param name="directoryPath">要压缩的目录</param>
        /// <param name="zipedOutputStream">压缩对象</param>
        private void ZipDirectoryFiles(string directoryPath, ZipOutputStream zipedOutputStream)
        {
            string[] filePaths = Directory.GetFiles(directoryPath);


            foreach (var singleFilePath in filePaths)
            {

                if (_config.IgnoreFiles.Exists(path =>
                {
                    return Path.GetExtension(singleFilePath) == path;
                })) continue;

                using (FileStream fs = File.OpenRead(singleFilePath))
                {
                    // 创建压缩条目
                    var zipPath = singleFilePath.Replace($"{ Config.DirectoryPath }\\", "");

                    ZipEntry entry = new ZipEntry(zipPath);
                    entry.DateTime = DateTime.Now;
                    entry.Size = fs.Length;
                    zipedOutputStream.PutNextEntry(entry);

                    // 设置默认缓冲大小和记录读取长度的变量
                    var blockSize = 2048;
                    var rededBytesCount = 0;
                    var totalRededBytesCount = 0;

                    // 读取的文件字节数组
                    var fileBytes = new byte[fs.Length];

                    while (totalRededBytesCount < fs.Length)
                    {
                        // 如果剩余字节流长度不够了就设置为剩余的长度，否则就是缓冲的块大小
                        blockSize = fs.Length - totalRededBytesCount > blockSize ? blockSize : (int)(fs.Length - totalRededBytesCount);
                        rededBytesCount = fs.Read(fileBytes, totalRededBytesCount, blockSize);
                        totalRededBytesCount += rededBytesCount;
                    }

                    // 写入压缩文件                    
                    zipedOutputStream.Write(fileBytes, 0, fileBytes.Length);
                    zipedOutputStream.Flush();
                }
            }
        }

        /// <summary>
        /// 递归压缩子目录及子目录下的文件
        /// </summary>
        /// <param name="directoryPath"></param>
        /// <param name="zipedOutputStream"></param>
        private void ZipSubDirectories(string directoryPath, ZipOutputStream zipedOutputStream)
        {
            // 创建子目录的条目            
            //ZipEntry entry = new ZipEntry($"{directoryPath.Replace(Config.DirectoryPath, "")}/");
            //zipedOutputStream.PutNextEntry(entry);
            //zipedOutputStream.Flush();

            // 压缩目录下的文件
            ZipDirectoryFiles(directoryPath, zipedOutputStream);

            var subDirectories = Directory.GetDirectories(directoryPath);

            if (subDirectories.Length > 0)
            {
                foreach (var subDirectoryPath in subDirectories)
                {
                    // 压缩子目录
                    ZipSubDirectories(subDirectoryPath, zipedOutputStream);
                }
            }
        }

        #endregion

        /// <summary>
        /// 解压文件
        /// </summary>
        /// <param name="targetFile"></param>
        /// <param name="fileDir"></param>
        /// <returns></returns>
        public bool UnZipFile(string targetFile, string fileDir)
        {
            bool result;
            try
            {
                //解决文件被进程占用问题
                //FileStream fr = new FileStream(fileDir, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                //读取压缩文件(zip文件)，准备解压缩  
                FileStream fr = new FileStream(targetFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                ZipInputStream s = new ZipInputStream(fr);
                ZipEntry theEntry;
                string path = fileDir;
                //解压出来的文件保存的路径  

                string rootDir = " ";
                //根目录下的第一个子文件夹的名称  
                while ((theEntry = s.GetNextEntry()) != null)
                {
                    rootDir = Path.GetDirectoryName(theEntry.Name);
                    //得到根目录下的第一级子文件夹的名称  
                    if (rootDir.IndexOf("\\") >= 0)
                    {
                        rootDir = rootDir.Substring(0, rootDir.IndexOf("\\") + 1);
                    }
                    string dir = Path.GetDirectoryName(theEntry.Name);
                    //根目录下的第一级子文件夹的下的文件夹的名称  
                    string fileName = Path.GetFileName(theEntry.Name);
                    //根目录下的文件名称  
                    if (dir != " ")
                    //创建根目录下的子文件夹,不限制级别  
                    {
                        if (!Directory.Exists(fileDir + "\\" + dir))
                        {
                            path = fileDir + "\\" + dir;
                            //在指定的路径创建文件夹  
                            Directory.CreateDirectory(path);
                        }
                    }
                    else if (dir == " " && fileName != "")
                    //根目录下的文件  
                    {
                        path = fileDir;
                    }
                    else if (dir != " " && fileName != "")
                    //根目录下的第一级子文件夹下的文件  
                    {
                        if (dir.IndexOf("\\") > 0)
                        //指定文件保存的路径  
                        {
                            path = fileDir + "\\" + dir;
                        }
                    }

                    if (dir == rootDir)
                    //判断是不是需要保存在根目录下的文件  
                    {
                        path = fileDir + "\\" + rootDir;
                    }

                    //以下为解压缩zip文件的基本步骤  
                    //基本思路就是遍历压缩文件里的所有文件，创建一个相同的文件。  
                    if (fileName != String.Empty)
                    {
                        FileStream streamWriter = File.Create(path + "\\" + fileName);

                        int size = 2048;
                        byte[] data = new byte[2048];
                        while (true)
                        {
                            size = s.Read(data, 0, data.Length);
                            if (size > 0)
                            {
                                streamWriter.Write(data, 0, size);
                            }
                            else
                            {
                                break;
                            }
                        }

                        streamWriter.Close();
                    }
                }
                s.Dispose();
                s.Close();
                result = true; ;
            }
            catch(Exception ex) 
            {
                throw ex;
            }
            return result;
        }

        /// <summary>
        ///  解压文件 
        /// </summary>
        /// <param name="targetFile"></param>
        /// <param name="fileDir"></param>
        /// <returns></returns>
        public bool FastUnZipFile(string targetFile, string fileDir)
        {
            bool result = false;
            try
            {
                (new FastZip()).ExtractZip(fileDir, targetFile, "");
                result = true;
            }
            catch
            {
                result = false; ;
            }
            return result;
        }
    }
}
